Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "service context" support for exec and health checks #246

Merged
merged 12 commits into from
Jul 11, 2023

Conversation

benhoyt
Copy link
Contributor

@benhoyt benhoyt commented Jun 26, 2023

This adds the ability to run exec commands (one-shot commands) and exec-based health checks using the context from an existing service, specifically, reusing its environment variables, user/group, and working directory. Any options in the exec command or health check, if present, override the service context (and the environment variables are merged).

This is based on spec OP034.

Once this is merged, we should update the Pebble version in Juju. Update: done in juju/juju#15909

benhoyt added a commit to benhoyt/operator that referenced this pull request Jun 26, 2023
This is Pebble PR canonical/pebble#246

Also add working_dir field to pebble.Service and flesh out types for
HttpDict, TcpDict, and ExecDict.
client/exec.go Outdated Show resolved Hide resolved
internals/plan/plan.go Outdated Show resolved Hide resolved
internals/plan/plan.go Outdated Show resolved Hide resolved
internals/plan/plan.go Show resolved Hide resolved
Copy link
Contributor

@niemeyer niemeyer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super nice work, folks.

What follows is a couple of trivial suggestions, and one bug that is unrelated to this PR.

I would also like to brainstorm/bikeshed quickly about the "context" name next Tuesday, but a preemptive +1 on the PR already.

@@ -65,7 +65,7 @@ func (m *CheckManager) PlanChanged(p *plan.Plan) {
ctx, cancel := context.WithCancel(context.Background())
check := &checkData{
config: config,
checker: newChecker(config),
checker: newChecker(config, p),
ctx: ctx,
cancel: cancel,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not about this PR, but looking at this logic makes me think there's a bug here that needs to be addressed separately. The cancel here is a request. Right above we're calling cancel() as if this would be a synchronous operation, but it's not. The goroutine may take its time to run. This may cause very awkward outcomes, such as an old check running after a new check. We need to use tomb or something similar to make the stopping of checkers become synchronous.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed! @flotter Actually fixed this exact bug a couple of weeks ago. It was causing intermittent issues in CI, but of course as you point out it could be a problem in production too.

@flotter Note that in future (perhaps for your servstate fix :-) we should probably use tombs for this rather than wait groups, to be consistent with other parts of Pebble. Pebble already uses tombs extensively elsewhere, so it won't be a new dependency. I should have mentioned that in the code review.

Group: config.Exec.Group,
WorkingDir: config.Exec.WorkingDir,
}
merged, err := plan.MergeServiceContext(p, config.Exec.Context, overrides)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a very clean implementation. Thank you.

internals/plan/plan.go Show resolved Hide resolved
@@ -486,6 +487,9 @@ func (c *ExecCheck) Merge(other *ExecCheck) {
if other.Command != "" {
c.Command = other.Command
}
if other.Context != "" {
c.Context = other.Context
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only thing I'm not sure about here is if we want to support a context: default variant to allow a new layer to reset the context value back to the default (don't use any service context). Otherwise if one layer sets it, there's no way to unset it.

My inclination is that it's very unlikely to be used and we should leave it out for now.


// MergeServiceContext merges the overrides on top of the service context
// specified by serviceName, returning a new ContextOptions value.
func MergeServiceContext(p *Plan, serviceName string, overrides ContextOptions) (ContextOptions, error) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the shared helper function for both exec (one-shot commands) and exec-type health checks, used to merge the specified overrides on top of the service context values.

internals/plan/plan.go Outdated Show resolved Hide resolved
internals/plan/plan.go Show resolved Hide resolved
client/exec.go Outdated Show resolved Hide resolved
internals/cli/cmd_exec.go Outdated Show resolved Hide resolved
internals/plan/plan.go Outdated Show resolved Hide resolved
@@ -65,7 +65,7 @@ func (m *CheckManager) PlanChanged(p *plan.Plan) {
ctx, cancel := context.WithCancel(context.Background())
check := &checkData{
config: config,
checker: newChecker(config),
checker: newChecker(config, p),
ctx: ctx,
cancel: cancel,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed! @flotter Actually fixed this exact bug a couple of weeks ago. It was causing intermittent issues in CI, but of course as you point out it could be a problem in production too.

@flotter Note that in future (perhaps for your servstate fix :-) we should probably use tombs for this rather than wait groups, to be consistent with other parts of Pebble. Pebble already uses tombs extensively elsewhere, so it won't be a new dependency. I should have mentioned that in the code review.

internals/plan/plan.go Show resolved Hide resolved
@benhoyt
Copy link
Contributor Author

benhoyt commented Jul 9, 2023

Code review changes address. This is done now, but I'll wait till after Tuesday to merge in case we decide on a different name than "context" during the bikeshed.

@benhoyt
Copy link
Contributor Author

benhoyt commented Jul 11, 2023

Updated "context" to "service-context" per discussion.

@benhoyt benhoyt merged commit 00bcd1f into canonical:master Jul 11, 2023
@benhoyt benhoyt deleted the service-context branch July 11, 2023 09:35
jujubot added a commit to juju/juju that referenced this pull request Jul 12, 2023
#15909

Relevant changes for use of Pebble in Juju:

* fix: exec and checks inherit environment from daemon (canonical/pebble#234)
* Allow non-root to specify user/group to itself (canonical/pebble#239)
* Add support for services to configure working directory (canonical/pebble#245)
* Improve error messages with exec, especially if working dir missing (canonical/pebble#236)
* Add "service context" support for exec and health checks (canonical/pebble#246)

Full list of changes [here](canonical/pebble@5842ea6...00bcd1f).

Note that this updates the version of `canonical/x-go` as well as switching to Canonical fork of `go-flags`.
benhoyt added a commit to canonical/operator that referenced this pull request Jul 17, 2023
This is Pebble PR canonical/pebble#246

Also add working_dir field to pebble.Service and flesh out types for
HttpDict, TcpDict, and ExecDict.

Bump up to latest Pyright version while we're at it

It will be supported on Juju 3.1.6+ and 3.2.1+ and 3.3+
@benhoyt benhoyt mentioned this pull request Sep 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants